#include <linux/hrtimer.h>
#include <linux/ktime.h>
#include <linux/gpio.h>
#include <linux/slab.h>

#include "pwm.h"

// Work in progress bitbang for gpio_pwm. Having to reinvent
// the wheel until ehrpwm drivers are made for the am335x.

static enum hrtimer_restart hrtimer_callback(struct hrtimer *timer)
{
	gpio_pwm_desc *desc = container_of(timer, struct gpio_pwm_desc_t, hr_timer);
	ktime_t tnew;

	if(desc->duty_ticks != desc->period_ticks && desc->duty_ticks != 0) {
		if(desc->active) {
			gpio_set_value(desc->gpio_pin, desc->polarity ? 1 : 0);
			tnew = ktime_set(0, desc->duty_ticks);
			desc->active = 0;
		}
		else {
			gpio_set_value(desc->gpio_pin, desc->polarity ? 0 : 1);
			tnew = ktime_set(0, desc->period_ticks - desc->duty_ticks);
			desc->active = 1;
		}
	}
	else {
		if(desc->duty_ticks == desc->period_ticks) {
			desc->active = desc->polarity;
			gpio_set_value(desc->gpio_pin, desc->polarity ? 1 : 0);
		}
		else {
			desc->active = ~desc->polarity;
			gpio_set_value(desc->gpio_pin, desc->polarity ? 0 : 1);
		}

		return HRTIMER_NORESTART;
	}
	hrtimer_start(&desc->hr_timer, tnew, HRTIMER_MODE_REL);

	return HRTIMER_NORESTART;
}

gpio_pwm_desc* gpio_pwm_init(int gpio_pin)
{
	gpio_pwm_desc *desc;
	//char *buf;
	//sprintf(buf,"GPIO_PWM %d",gpio_pin);
	if(!gpio_request(gpio_pin,"GPIO_1")) {
		desc = (gpio_pwm_desc*)kmalloc(sizeof(gpio_pwm_desc),GFP_KERNEL);
		memset(desc, 0, sizeof(gpio_pwm_desc));
		gpio_direction_output(gpio_pin,0);
		desc->gpio_pin = gpio_pin;

		hrtimer_init(&desc->hr_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
		// now a container_of desc->hr_timer will return desc
		desc->hr_timer.function = &hrtimer_callback;

		return desc;
	}
	else
		return (gpio_pwm_desc*)-ENODEV;
}

void gpio_pwm_start(gpio_pwm_desc *desc)
{
	if(desc->period_ticks == 0) return;

	hrtimer_callback(&desc->hr_timer);
}

void gpio_pwm_set_duty(gpio_pwm_desc *desc, int ticks)
{
	desc->duty_ticks = ticks;
}

void gpio_pwm_set_period(gpio_pwm_desc *desc, int ticks)
{
	if(ticks == 0) return;
	desc->period_ticks = ticks;
}

void gpio_pwm_set_polarity(gpio_pwm_desc *desc, int polarity)
{
	desc->polarity = polarity;
}

void gpio_pwm_stop(gpio_pwm_desc *desc)
{
	hrtimer_cancel(&desc->hr_timer);
	gpio_set_value(desc->gpio_pin, 0);
}

void gpio_pwm_release(gpio_pwm_desc *desc)
{
	hrtimer_cancel(&desc->hr_timer);
	gpio_set_value(desc->gpio_pin, 0);
	gpio_free(desc->gpio_pin);
	kfree(desc);
}
